#include <algorithm>
#include "Player.h"
#include "GlobalTimer.h"
#include "OnMouseLeftDownEvent.h"
#include "OnMouseRightDownEvent.h"
#include "OnKeyPressEvent.h"
#include "GameApplication.h"
#include "PlayerController.h"
#include "DestinationReachedEvent.h"
#include "AddDestinationEvent.h"
#include "SetDestinationEvent.h"
#include "TargettedAbility.h"
#include "CombatRequest.h"
#include "CombatSystem.h"

#define SECONDS_BETWEEN_MOVE_UPDATE 1
#define SECONDS_BETWEEN_PATH_UPDATE 4

Player::Player( PlayerController* controller ) : m_controller(controller), m_state(DEFAULT), m_mouseState(SELECTION), m_autoAttackFlag(true)
{
}
	
HashedString Player::EnemyGroup()
{
	return m_enemyGroup;
}
HashedString Player::Group()
{
	return m_group;
}
void Player::EnemyGroup( HashedString& group )
{
	m_enemyGroup = group;
}
void Player::Group( HashedString& group)
{
	m_group = group;
}

bool Player::OnMouseLeftDown( IEvent& event )
{
	OnMouseLeftDownEvent& mouseEvent = static_cast<OnMouseLeftDownEvent&>(event);

	m_selected = Game.Select(mouseEvent.Coords().X, mouseEvent.Coords().Y);

	std::list<Entity*>& enemies = Game.getEntityGroup(m_enemyGroup);

	if (m_mouseState == ATTACK_MOVE && std::find(enemies.begin(), enemies.end(), m_selected) != enemies.end())
	{
		m_state = AUTO_ATTACKING;
		m_engagedEnemy = m_selected;
	}
	return true;
}

bool Player::OnMouseRightDown( IEvent& event )
{
	OnMouseRightDownEvent& mouseEvent = static_cast<OnMouseRightDownEvent&>(event);
	
	Maths::Vector3D position;
	Entity* selected = Game.Select(mouseEvent.Coords().X, mouseEvent.Coords().Y, position);
	
	std::list<Entity*>& enemies = Game.getEntityGroup(m_enemyGroup);
	std::list<Entity*>& team = Game.getEntityGroup(m_group);

	if (std::find(enemies.begin(), enemies.end(), selected) != enemies.end())
	{	
		m_state = AUTO_ATTACKING;
		m_engagedEnemy = selected;
	} else if (std::find(team.begin(), team.end(), selected) != team.end())
	{	
		m_state = FOLLOWING;
		m_followingEntity = selected;
	} else
	{
		if (mouseEvent.ShiftKey())
		{
			m_controller->ReceiveEvent(AddDestinationEvent(position));
		} else
		{
			m_controller->ReceiveEvent(SetDestinationEvent(position));
		}
	}
	return true;
}

bool Player::OnKeyPress( IEvent& event )
{
	OnKeyPressEvent& keyEvent = static_cast<OnKeyPressEvent&>(event);

	if (keyEvent.Key() == 'a')
		m_mouseState = ATTACK_MOVE;
	return true;
}

Entity* Player::FindClosestEnemy()
{	
	Entity* enemy = 0;
	OrientationComponent* orientation = 0;
	Maths::Vector3D position;
	float distance;
	float closestDistance = FLT_MAX;

	m_controller->getPosition(position);
	std::list<Entity*>& enemies = Game.getEntityGroup(m_enemyGroup);

	for (std::list<Entity*>::iterator it = enemies.begin(); it != enemies.end(); ++it)
	{
		orientation = (OrientationComponent*)((*it)->FindComponent(OrientationComponent::type));
		Maths::Vector3D enemyPosition = orientation->getPosition().getObject();

		distance = position.DistanceSqr(enemyPosition);

		if (distance < closestDistance)
		{
			closestDistance = distance;
			enemy = (*it);
		}
	}
	return enemy;
}	

inline static float getDistanceBetween( Entity* entity, Maths::Vector3D& position )
{
	OrientationComponent* orientation = (OrientationComponent*)((entity)->FindComponent(OrientationComponent::type));
	Maths::Vector3D entityPosition = orientation->getPosition().getObject();

	return position.DistanceSqr(entityPosition);
}

bool Player::Destination( IEvent& event )
{
	if (event.Type() == SetDestinationEvent::type)
	{
		SetDestinationEvent& setDestination = static_cast<SetDestinationEvent&>(event);
		
		Maths::Vector3D destination = setDestination.Destination();

		m_controller->UpdateDestination(destination);
		m_controller->UpdatePath();	
	}
	return false;
}

bool Player::DestinationReached( IEvent& event )
{
	if (event.Type() == DestinationReachedEvent::type)
	{
		std::list<Maths::Vector3D>& waypoints = m_controller->getWaypoints();
		if (waypoints.size() > 0)
		{
			Maths::Vector3D next = waypoints.front();
			waypoints.pop_front();

			m_controller->ReceiveEvent(SetDestinationEvent(next));
		} else
		{
			Maths::Vector3D position;
			m_controller->getPosition(position);
			m_controller->ReceiveEvent(SetDestinationEvent(position));
		}
	}
	return false;
}

bool Player::AddDestination( IEvent& event )
{
	if (event.Type() == AddDestinationEvent::type)
	{
		AddDestinationEvent& addDestination = static_cast<AddDestinationEvent&>(event);
		Maths::Vector3D destination = addDestination.Destination();

		std::list<Maths::Vector3D>& waypoints = m_controller->getWaypoints();
		waypoints.push_back(destination);
	}
	return false;
}

void Player::Update()
{
	// Time to update?
	float secondsSinceStart = GlobalTimer::getTime().getSecondsSinceStart();

	switch (m_state)
	{
	case DEFAULT:
		{
			if (secondsSinceStart > (m_lastMoveTime.getSecondsSinceStart() + SECONDS_BETWEEN_MOVE_UPDATE) && !(m_controller->Vehicle().Sleeping()))
			{
				int speed = 0;
				Maths::Vector3D direction;

				m_controller->getStatus("Speed", speed);							// retreive speed
				m_controller->getDirection(direction);								// retreive direction

				if (direction == Maths::Vector3D::ZERO && m_controller->getWaypoints().size() > 0)
					m_controller->ReceiveEvent(DestinationReachedEvent());

				m_controller->UpdateVelocity(direction * (float)speed);				// set new velocity

				m_lastMoveTime = GlobalTimer::getTime();
			}
			
			// Time to update?
			if (secondsSinceStart > (m_lastPathTime.getSecondsSinceStart() + SECONDS_BETWEEN_PATH_UPDATE))
			{
				m_controller->UpdatePath();
				m_lastPathTime = GlobalTimer::getTime();
			}
			
			if (m_autoAttackFlag && !m_engagedEnemy)
			{
				Entity* enemy = FindClosestEnemy();

				if (enemy)
				{
					Maths::Vector3D position;
					m_controller->getPosition(position);

					float distance = getDistanceBetween(enemy, position);
					
					Combat::IAbility::Vector_t& abilities = m_controller->getAbilities();
					Combat::TargettedAbility* actAbility = (Combat::TargettedAbility*)((Combat::IAbility*)abilities[0]);
					float range = actAbility->Range();

					if (distance <= range * range)
					{
						m_state = AUTO_ATTACKING;
						m_engagedEnemy = enemy;
					} else
					{
					}
				}
			}

			break;
		}
	case AUTO_ATTACKING:
		{
			if (!Game.EntityExists(m_engagedEnemy))
			{
				m_state = DEFAULT;
				m_engagedEnemy = 0;
				return;
			}

			int attackSpeed = 0;
			m_controller->getStatus("AttackSpeed", attackSpeed);
			float actualAttackSpeed = (float)attackSpeed/100.0f;

			Maths::Vector3D position;
			m_controller->getPosition(position);
			float distance = getDistanceBetween(m_engagedEnemy, position);

			Combat::IAbility::Vector_t& abilities = m_controller->getAbilities();

			Combat::TargettedAbility* actAbility = (Combat::TargettedAbility*)((Combat::IAbility*)abilities[0]);
			float range = actAbility->Range();

			if (distance <= range * range)
			{
				if (secondsSinceStart > m_lastAutoAttackTime.getSecondsSinceStart() + actualAttackSpeed)
				{
					DoAutoAttack();
				}
				m_controller->UpdateVelocity(Maths::Vector3D(Maths::Vector3D::ZERO));				// set new velocity
			} else
			{
				int speed = 0;
				OrientationComponent* orientation = (OrientationComponent*)((m_engagedEnemy)->FindComponent(OrientationComponent::type));
				Maths::Vector3D entityPosition = orientation->getPosition().getObject();
				
				m_controller->getStatus("Speed", speed);							// retreive speed
				Maths::Vector3D direction = (entityPosition - position).Normal();

				m_controller->UpdateVelocity(direction * (float)speed);				// set new velocity
			}
			break;
		}
	case FOLLOWING:
		{
			Maths::Vector3D position;
			m_controller->getPosition(position);

			int speed = 0;
			OrientationComponent* orientation = (OrientationComponent*)((m_followingEntity)->FindComponent(OrientationComponent::type));
			Maths::Vector3D entityPosition = orientation->getPosition().getObject();
				
			m_controller->getStatus("Speed", speed);							// retreive speed
			Maths::Vector3D direction = (entityPosition - position).Normal();

			m_controller->UpdateVelocity(direction * (float)speed);				// set new velocity
			break;
		}
	}
}

void Player::DoAutoAttack()
{
	CombatComponent* target = (CombatComponent*)(m_engagedEnemy->FindComponent(CombatComponent::type));
	CombatComponent* me = (CombatComponent*)(m_controller->getEntity()->FindComponent(CombatComponent::type));

	if (target && me)
	{
		Combat::TargettedRequest* targettedRequest;
		Pointer<Combat::Request> request = targettedRequest = new Combat::TargettedRequest();

		targettedRequest->ability = 0;
		targettedRequest->requester = me->Combatant();
		targettedRequest->target = target->Combatant();

		CombatSystem.SubmitRequest(request);

		m_lastAutoAttackTime = GlobalTimer::getTime();
	}
}